Android okhttp使用详解

Get 请求

1
2
3
4
5
6
7
Request request = new Request.Builder()
.url(url)
.build();
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}

通过Request构建请求体,然后由 client 执行。这里 Request 默认请求类型是Get。注意这个是SynchronousGet是同步执行的。在Android UI线程直接运行会报错提示:==android.os.NetworkOnMainThreadException==。

异步Get请求 AsynchronousGet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
System.out.println(responseBody.string());
}
}
});

异步请求会在Android 中开启一个线程队列,在子线程中运行,不影响Android UI 线程。注意的是Callback类回调仍然执行在子线程中,需要通过handler处理UI线程相关操作。

Post 请求

注意下述代码中演示的同步请求,通过client.newCall(request).enqueue(Callback)可以进行异步请求

Post 提交 json 请求体

1
2
3
4
5
6
7
8
9
10
11
12
MediaType JSON = MediaType.get("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, json);

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.post(body)
.build();

try (Response response = client.newCall(request).execute()) {
return response.body().string();
}

根据string类型的json字符串请求请求体,通过post(ReqeustBody)方法请求数据。

Post 提交 String 请求体

注意创建的MediaType类型,MediaType类型根据自己需求创建。这里定义为markdown类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
MediaType MEDIA_TYPE_MARKDOWN = MediaType.get("text/x-markdown; charset=utf-8");
OkHttpClient client = new OkHttpClient();

Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}

Post 提交Form表单(FormBody)

1
2
3
4
5
6
7
8
9
10
11
12
13
RequestBody formBody = new FormBody.Builder()
.add("md5", "12232")
.build();

Request request = new Request.Builder()
.url("http://221.130.29.94:8090/bookQr/test/isUpload")
.post(formBody)
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}

Post 提交File文件

1
2
3
4
5
6
7
8
9
10
11
12
13
MediaType MEDIA_TYPE_MARKDOWN= MediaType.get("text/x-markdown; charset=utf-8");
OkHttpClient client = new OkHttpClient();

File file = new File("README.md");
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}

Post 提交多种类型文件(MultiPartBody)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MediaType MEDIA_TYPE_PNG = MediaType.get("image/png");
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("title", "Square Logo")
.addFormDataPart("image", "logo-square.png",
RequestBody.create(MEDIA_TYPE_PNG, new File(filePath)))
.build();

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.imgur.com/3/image")
.post(requestBody)
.build();

try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}

##访问请求头内容(Acessing Header)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Request request = new Request.Builder()
.url("https://api.github.com/repos/square/okhttp/issues")
.header("User-Agent", "OkHttp Headers.java")
.addHeader("Accept", "application/json; q=0.5")
.addHeader("Accept", "application/vnd.github.v3+json")
.build();
OkHttpClient client = new OkHttpClient();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println("Server: " + response.header("Server"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Vary: " + response.headers("Vary"));
}

Request.Builder#header()和Request.Builder#addHeader()区别,header()方法添加的key只有一个,多次添加改变value值,addHeader() 会为当前key再添加一个值。对于Response#header()和Response#headers()区别:header()方法会打印对于key出现的最后一次值,而headers()会将对应key的所有值列出来。

okhttp拦截器 - Interceptor

##添加应用拦截器

1
2
3
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor())
.build();

如何实现自定义拦截器,我们需要实现系统提供的 Interceptor 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static class LoggingInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
long t1 = System.nanoTime();
Request request = chain.request();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);

long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
request.url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}

当我们发起网络请求,打印日志如下,request#headers()值为空:

1
2
3
4
5
6
7
8
9
10
11
12
Sending request http://www.publicobject.com/helloworld.txt on null
User-Agent: OkHttp Sample

Received response for http://www.publicobject.com/helloworld.txt in 2957.8ms
Server: nginx/1.10.0 (Ubuntu)
Date: Mon, 01 Jul 2019 02:52:58 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes

##添加网络拦截器

添加网络拦截器如下所示:

1
2
3
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new LoggingInterceptor())
.build();

打印日志信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Sending request http://www.publicobject.com/helloworld.txt on Connection{www.publicobject.com:80, proxy=DIRECT hostAddress=www.publicobject.com/54.187.32.157:80 cipherSuite=none protocol=http/1.1}
User-Agent: OkHttp Sample
Host: www.publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

Received response for http://www.publicobject.com/helloworld.txt in 271.5ms
Server: nginx/1.10.0 (Ubuntu)
Date: Mon, 01 Jul 2019 02:54:12 GMT
Content-Type: text/html
Content-Length: 194
Connection: keep-alive
Location: https://publicobject.com/helloworld.txt

Sending request https://publicobject.com/helloworld.txt on Connection{publicobject.com:443, proxy=DIRECT hostAddress=publicobject.com/54.187.32.157:443 cipherSuite=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 protocol=http/1.1}
User-Agent: OkHttp Sample
Host: publicobject.com
Connection: Keep-Alive
Accept-Encoding: gzip

Received response for https://publicobject.com/helloworld.txt in 176.9ms
Server: nginx/1.10.0 (Ubuntu)
Date: Mon, 01 Jul 2019 02:54:13 GMT
Content-Type: text/plain
Content-Length: 1759
Last-Modified: Tue, 27 May 2014 02:35:47 GMT
Connection: keep-alive
ETag: "5383fa03-6df"
Accept-Ranges: bytes

网络拦截器打印了两次日志信息,第一个地址是http://www.publicobject.com/helloworld.txt,第二个地址是https://publicobject.com/helloworld.txt

Application interceptors(应用拦截器)

  • 不必担心中间的responses,例如重定向和重连。
  • 总是调用一次,即使是从缓存HTTP响应。
  • 观察应用程序的原始意图。不关心OkHttp的注入headers,例如If-None-Match
  • 允许短路和不执行Chain.proceed().
  • 允许重连,多次调用proceed()。

Network Interceptors (网络拦截器)

  • 能够操作中间反应,例如重定向和重连。
  • 不能被缓存响应,例如短路网络调用(short-circuit the network)。
  • 观察数据,正如它将在网络上传输。
  • 访问携带request的Connection

注意在进行okhttp请求获取下载进度,根据Callback返回

缓存响应数据(Reponse Caching)

To cache responses, you’ll need a cache directory that you can read and write to, and a limit on the cache’s size. The cache directory should be private, and untrusted applications should not be able to read its contents!

我们需要限制缓存的大小,并且缓存数据的目录不可以被其他应用读取。

Response caching uses HTTP headers for all configuration. You can add request headers like Cache-Control: max-stale=3600 and OkHttp’s cache will honor them. Your webserver configures how long responses are cached with its own response headers, like Cache-Control: max-age=9600. There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.

我们可以手动为request请求添加请求头”Cache-Control: max-stale=3600”,设置缓存时间。或者由服务器决定我们的缓存时间。max-stale是发起请求方设置的缓存时间,只对请求端有效。max-age为设置相应数据的缓存有效期时间,在没有超过max-age的情况下,会自动从缓存中读数据;在超出max-age的情况会向服务端发送新的请求,如果请求失败会返回缓存的数据。如果max-age有效期过了,请求端设置了max-stale,仍然可以从缓存中读取,不需要去服务器请求新的内容。因此max-agemax-stale在请求中同时使用的情况下,缓存的时间为max-agemax-stale的和,超出max-age值会提示Warning: 110 HttpURLConnection "Response is stalemax-age和max-stale详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(cacheDirectory, cacheSize);

OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();

Request request = new Request.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();

String response1Body;
try (Response response1 = client.newCall(request).execute()) {
if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

response1Body = response1.body().string();
System.out.println("Response 1 response: " + response1);
System.out.println("Response 1 cache response: " + response1.cacheResponse());
System.out.println("Response 1 network response: " + response1.networkResponse());
}

String response2Body;
try (Response response2 = client.newCall(request).execute()) {
if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

response2Body = response2.body().string();
System.out.println("Response 2 response: " + response2);
System.out.println("Response 2 cache response: " + response2.cacheResponse());
System.out.println("Response 2 network response: " + response2.networkResponse());
}
System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));

response2的数据将从缓存中获取,日志信息如下:

1
2
3
4
5
6
7
Response 1 response:          Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 1 network response: Response{protocol=http/1.1, code=304, message=Not Modified, url=https://publicobject.com/helloworld.txt}
Response 2 response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 cache response: Response{protocol=http/1.1, code=200, message=OK, url=https://publicobject.com/helloworld.txt}
Response 2 network response: null
Response 2 equals Response 1? true
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×